package com.amos.doublewriterconsistence.service.impl;

import com.amos.doublewriterconsistence.request.Request;
import com.amos.doublewriterconsistence.request.RequestQueue;
import com.amos.doublewriterconsistence.request.impl.InventoryCacheRequest;
import com.amos.doublewriterconsistence.request.impl.InventoryDBRequest;
import com.amos.doublewriterconsistence.service.RequestAsyncProcessService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;

/**
 * Copyright © 2020 心情如水. All rights reserved.
 *
 * @Project: double-writer-consistence
 * @ClassName: RequestAsyncProcessServiceImpl
 * @Package: com.amos.doublewriterconsistence.service.impl
 * @author: QIXIAOWEI
 * @Description: 【请求异步处理的service实现】
 * 所有的请求我们都需要打入到缓存队列中来执行下，所以同一库存，我们需要他打入到同一缓存队列中进行处理，
 * 使用hash值取模的方式来实现
 * @date: 2020/2/19 0021 下午 21:22
 * @Version: V1.0
 */
@Service
public class RequestAsyncProcessServiceImpl implements RequestAsyncProcessService {

    public final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 路由到指定的缓存队列中
     * doubleWriterConsistence
     *
     * @param request
     */
    @Override
    public void route(Request request) {
        try {
            // 先做读请求的去重
            RequestQueue requestQueue = RequestQueue.getInstance();
            //获得内存队列中的-标志位
            Map<String, Boolean> flagMap = requestQueue.getTagMap();

             //instanceof只能用来判断对象是否属于某个类型
            if(request instanceof InventoryDBRequest) {
                // 如果是一个更新数据库的请求，那么就将那个productId对应的标识设置为true
                flagMap.put(request.getInventoryId(),true);
            } else if(request instanceof InventoryCacheRequest) {
                //请求缓存中的数据
                Boolean flag = flagMap.get(request.getInventoryId());
                // 如果flag是null,则说明读取缓存的操作,将标志位库存ID的值修改为False,因为更新数据库置的为True
                if(flag == null) {
                    flagMap.put(request.getInventoryId(), Boolean.FALSE);
                }
                // 如果是缓存刷新的请求，那么就判断，如果标识不为空，而且是true，就说明之前有一
                //个这个商品的数据库更新请求
                if(flag != null && flag) {
                    flagMap.put(request.getInventoryId(), Boolean.FALSE);
                }
                // 如果是缓存刷新的请求，而且发现标识不为空，但是标识是false
                // 说明前面已经有一个缓存刷新请求了(数据库+缓存的请求了)
                //那么这个请求应该是读请求，可以直接过滤掉了，不要添加到队列中
                if(flag != null && !flag) {
                    // 对于这种读请求，直接就过滤掉，不要放到后面的内存队列里面去了
                    return;
                }
            }
            // 做请求的路由，根据每个请求的商品id，路由到对应的内存队列中去
            ArrayBlockingQueue<Request> queue = getRoutingQueue(request.getInventoryId());
            // 将请求放入对应的队列中，完成路由操作
            queue.put(request);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取路由到的内存队列
     * 根据库存记录路由到指定的缓存队列
     *
     * @param key  内存队列
     * @return 内存队列
     */
    private ArrayBlockingQueue<Request> getRoutingQueue(String key) {
        /**
         * 请求内存队列
         * 请求的队列-使用单例模式来确保请求的队列的对象只有一个,获取 RequestQueue 对象
         */
        RequestQueue requestQueue = RequestQueue.getInstance();
        int h;
        int hash = (key == null) ? 0 : (h = key.hashCode()) ^ (h >> 16);
        // 对hash值取模，将hash值路由到指定的内存队列中，比如内存队列大小8
        // 用内存队列的数量对hash值取模之后，结果一定是在0~7之间
        // 所以任何一个商品id都会被固定路由到同样的一个内存队列中去的
        int index = (requestQueue.size() - 1) & hash;

        this.logger.info("===========日志===========: 路由内存队列，商品id=" + key + ", 队列索引=" + index);

        this.logger.info("路由的缓存队列为：{}", index);

        /**
         * requestQueue.getQueue(index) 获取指定内存队列
         */
        return requestQueue.getQueue(index);
    }
}
